import {
  CanActivate,
  ExecutionContext,
  HttpException,
  HttpStatus,
  Injectable,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from '../role/entities/role.entity';
import { In } from 'typeorm';
import { Permission } from './entities/permission.entity';
import { REQUIRE_PERMISSION } from '../../../common/decorators/require-permission.decorator';
import { ResponseMsgKey } from '../../../common/constants/api-response.constant';
import {
  ACCOUNT_ROLE_IDS,
  DEFAULT_ROLE_VALUE,
} from '../../../common/constants/auth.constant';
import { ConfigService } from '@nestjs/config';
import { generateCacheKey } from '../role/role.service';
import { Request } from 'express';

@Injectable()
export class PermissionGuard implements CanActivate {
  /** 初始化管理员角色值 */
  private readonly rootRoleValue: string;
  constructor(
    private reflector: Reflector,
    private readonly configService: ConfigService,
  ) {
    this.rootRoleValue = this.configService.get(
      'ADMIN_ROLE_VALUE',
      DEFAULT_ROLE_VALUE,
    );
  }

  async canActivate(context: ExecutionContext): Promise<boolean> {
    // 获取当前handler的元数据
    const requirePermissions = this.reflector.getAllAndOverride<string[]>(
      REQUIRE_PERMISSION,
      [context.getClass(), context.getHandler()],
    );
    if (!requirePermissions || !requirePermissions?.length) {
      return true;
    }
    try {
      const request = context.switchToHttp().getRequest<Request>();
      const roleIds = request[ACCOUNT_ROLE_IDS];
      if (!roleIds?.length) {
        throw new Error();
      }

      const roles = await Role.find({
        where: {
          id: In(roleIds),
        },
        relations: {
          permissions: true,
        },
        cache: {
          id: generateCacheKey(roleIds),
          milliseconds: 30 * 24 * 60 * 60 * 1000,
        },
      });
      const isRootRole = roles.some((r) => r.value === this.rootRoleValue);
      // super admin have all permissions
      if (isRootRole) return true;

      const permissionMap = roles.reduce<{
        [permissionName: string]: Permission;
      }>((memo, r) => {
        r.permissions.forEach((p) => {
          if (!memo[p.value]) {
            memo[p.value] = p;
          }
        });
        return memo;
      }, {});
      const isPermit = requirePermissions.some((r) => permissionMap[r]);
      if (!isPermit) {
        throw new Error();
      }
      return true;
    } catch {
      throw new HttpException(ResponseMsgKey.FORBIDDEN, HttpStatus.FORBIDDEN);
    }
  }
}
